package com.mimo.common.configuration.broadcast;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.mimo.common.configuration.broadcast.event.NotificationEvent;
import com.mimo.common.configuration.broadcast.listener.EventListener;

/**
 * 事件调度中心
 * <p>
 * 用于当前项目中替换ApplicationContext的EventPublisher
 * 
 * @author Hongyu
 */
public enum MultiCastRegistry {
  INSTANCE;

  private final Map<Class<? extends NotificationEvent>, List<EventListener<NotificationEvent>>> holders = new ConcurrentHashMap<>();

  @SuppressWarnings("unchecked")
  public synchronized void add(EventListener<? extends NotificationEvent>... listeners) {
    for (EventListener<? extends NotificationEvent> listener : listeners) {
      Class<? extends NotificationEvent> resolveType = this.resolveEventType(listener);
      holders.computeIfAbsent(resolveType, k -> new LinkedList<>()).add((EventListener<NotificationEvent>) listener);
    }
  }

  public synchronized void remove(EventListener<? extends NotificationEvent> listener) {
    Class<? extends NotificationEvent> resolveType = this.resolveEventType(listener);
    holders.computeIfAbsent(resolveType, k -> new LinkedList<>()).remove(listener);
  }

  /**
   * 返回当前上下文的监听器
   * 
   * @return
   */
  public List<EventListener<NotificationEvent>> getEventListeners() {
    return holders.entrySet().stream().flatMap(e -> e.getValue().stream()).collect(Collectors.toList());
  }

  public void clear() {
    holders.clear();
  }

  /**
   * 事件分发入口
   * 
   * @param events
   */
  public void publish(NotificationEvent... events) {
    for (NotificationEvent event : events) {
      Objects.requireNonNull(event, "event not null");
      holders.get(event.getClass()).forEach(listener -> listener.onEvent(event));
    }
  }

  /**
   * 根据添加的监听器获取其监听的具体事件类型
   * 
   * @param listener
   * @return
   */
  @SuppressWarnings("unchecked")
  private Class<? extends NotificationEvent> resolveEventType(EventListener<? extends NotificationEvent> listener) {
    return (Class<? extends NotificationEvent>) Stream.of(listener.getClass().getMethods())
        .filter(g -> Objects.equals("onEvent", g.getName())).filter(g -> {
          Class<?>[] pts = g.getParameterTypes();
          return pts.length == 1 && pts[0] != NotificationEvent.class
              && NotificationEvent.class.isAssignableFrom(pts[0]);
        }).findAny().get().getParameterTypes()[0];
  }

}
